#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <memory.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "z_logger.h"

int socket_connect(const char *ip_addr, uint16_t port) {
    int fd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in s_addr;
    bzero(&s_addr, sizeof(s_addr));
    s_addr.sin_family = AF_INET;
    inet_pton(AF_INET, ip_addr, &s_addr.sin_addr);
    s_addr.sin_port = htons(port);

    if (-1 == connect(fd, &s_addr, sizeof(struct sockaddr_in))) {
        close(fd);
        fd = -1;
    }

    log_info("connect to %s:%d, fd=%d", ip_addr, port, fd);

    return fd;
}

_Bool socket_loop(int fd_r, int fd_w) {
    uint8_t buf[1024];

    ssize_t size = recv(fd_r, buf, sizeof(buf), MSG_DONTWAIT);

    if (size > 0) {
        uint8_t *s = buf, *e = s + size;

        char *hex = malloc(size * 3 + 1);
        char *ptr = hex;
        for (uint8_t *p = s; p < e; ++p) {
            ptr += sprintf(ptr, "%02x ", *p);
        }
        log_trace("fd=%d, read=%d, hex: %s", fd_r, size, hex);
        free(hex);

        while (s < e) {
            size = send(fd_w, s, e - s, 0);
            if (size <= 0) {
                return false;
            } else {
                s += size;
            }
        }
    } else if (size == 0) {
        return false;
    } else if (size < 0) {
        if (EAGAIN != errno) {
            return false;
        }
    }

    return true;
}

void socket_close(int fd) {
    if (-1 != fd) {
        log_info("close fd=%d", fd);
        close(fd);
    }
}

int main(int argc, char *argv[]) {
    char *remote_ip;
    uint16_t remote_port = 2404;
    char *local_ip = "127.0.0.1";
    uint16_t local_port = remote_port;

    if (argc == 1) {
        printf("usage: %s <remote_ip> <remote_port> [local_ip] [local_port]\n", argv[0]);
        return EXIT_FAILURE;
    }

    for (int i = 1; i < argc; ++i) {
        switch (i) {
            case 1:
                remote_ip = argv[i];
                break;
            case 2:
                remote_port = atoi(argv[i]);
                local_port = remote_port;
                break;
            case 3:
                local_ip = argv[i];
                break;
            case 4:
                local_port = atoi(argv[i]);
                break;
            default:
                break;
        }
    }

    log_init();

    while (running) {
        int fd1 = socket_connect(remote_ip, remote_port);
        int fd2 = socket_connect(local_ip, local_port);

        if (-1 != fd1 && -1 != fd2) {
            while (running) {
                if (!socket_loop(fd1, fd2)) break;
                if (!socket_loop(fd2, fd1)) break;
                usleep(1000);
            }
        }

        socket_close(fd1);
        socket_close(fd2);
    }

    log_fini();

    return EXIT_SUCCESS;
}